#pragma once

#include <memory>
#include <tuple>
#include <string>
#include <vector>
#include <mongoc.h>

namespace as
{
namespace db
{

class MongoBsonObj;
class MongoIndexModelObj
{
public:
	MongoIndexModelObj() = delete;
	MongoIndexModelObj(mongoc_index_model_t *im);
	~MongoIndexModelObj();
	using Ptr = std::shared_ptr<MongoIndexModelObj>;
	static Ptr MakeIndexModel(MongoBsonObj *idx, MongoBsonObj *opts = nullptr);
private:
	mongoc_index_model_t *im_;
};

class MongoPoolObj
{
public:
	MongoPoolObj() = delete;
	MongoPoolObj(mongoc_client_pool_t *pool);
	~MongoPoolObj();
	mongoc_client_t* GetClient();
private:
	mongoc_client_pool_t *pool_ = nullptr;
    mongoc_client_t *client_ = nullptr;
};

class MongoCollectionObj
{
public:
	MongoCollectionObj() = delete;
	MongoCollectionObj(mongoc_collection_t *coll);
	~MongoCollectionObj();
	mongoc_collection_t* GetCollection();
	using Ptr = std::shared_ptr<MongoCollectionObj>;
private:
	mongoc_collection_t *coll_ = nullptr;
};

class MongoBsonObj
{
public:
	MongoBsonObj() = default;
	MongoBsonObj(const char *jsonstr);
	MongoBsonObj(const std::string &jsonstr);

	~MongoBsonObj();

	MongoBsonObj(const MongoBsonObj &) = delete;
	MongoBsonObj(const MongoBsonObj &&) = delete;
	MongoBsonObj& operator=(const MongoBsonObj &) = delete;
	using Ptr = std::shared_ptr<MongoBsonObj>;
	void SetJson(const char *jsonstr);
	std::string ToJson();

	auto GetError();
	bool HasError();
	unsigned int GetErrorCode();
	const char* GetErrorMessage();
	bson_t* operator*();
	operator bson_t*() { return val_; }
	bson_t *val_ = nullptr;
	bson_error_t err_;
};

class MongoCursorObj
{
public:
	MongoCursorObj(mongoc_cursor_t *cursor);;
	~MongoCursorObj();
	MongoCursorObj(const MongoCursorObj &) = delete;
	MongoCursorObj(const MongoCursorObj &&) = delete;
	MongoCursorObj& operator=(const MongoCursorObj &) = delete;
	using Ptr = std::shared_ptr<MongoCursorObj>;

	auto GetError();
	bool HasError();
	unsigned int GetErrorCode();
	const char* GetErrorMessage();

	bool Next(const bson_t **obj);
	bool Next(const MongoBsonObj *obj);
	bool More();

	mongoc_cursor_t* operator*() { return cursor_; };
	operator mongoc_cursor_t*() { return cursor_; }
private:
	mongoc_cursor_t *cursor_ = nullptr;
};

class MongoBsonIterObj;
class MongoBsonValue
{
public:
	using Ptr = std::shared_ptr<MongoBsonValue>;
	MongoBsonValue() {};
	MongoBsonValue(MongoBsonIterObj &obj);

	MongoBsonValue(const MongoBsonValue &obj);
	MongoBsonValue(MongoBsonValue &&obj);
	MongoBsonValue& operator=(const MongoBsonValue &obj);
	MongoBsonValue& operator=(MongoBsonValue &&obj)noexcept;

	int GetInt32();
	unsigned long long GetInt64();
	std::string GetString();
	double GetDouble();
private:
	bson_iter_t iter_;
};


class MongoBsonIterObj
{
public:
	friend class MongoBsonValue;
	using Ptr = std::shared_ptr<MongoBsonIterObj>;

	MongoBsonIterObj(MongoBsonObj &obj)
	{
		bson_ = *obj;
		Init();
	}
	~MongoBsonIterObj()
	{
	}

	MongoBsonValue::Ptr Find(const char *key)
	{
		if (Init() && bson_iter_find(&iter_, key))
		{
			return std::make_shared<MongoBsonValue>(*this);
		}
		return nullptr;
	}

	MongoBsonValue::Ptr Find(const std::string &key)
	{
		return Find(key.c_str());
	}

protected:
	bool Init()
	{
		return bson_iter_init(&iter_, bson_);
	}

private:
	bson_t *bson_;
	bson_iter_t iter_;
};

class MongoFlags
{
public:
	enum
	{
		FIND_OPTS_REMOVE = 0x1,
		FIND_OPTS_UPSERT = 0x2,
		FIND_OPTS_NEW = 0x4,
	};
	void SetDelete();
	void SetUpsert();
	void SetNew();

	bool IsDelete();
	bool IsUpsert();
	bool IsNew();

	int GetFlags();
private:
	int flags_ = 0;
};

class MongoAccess
{
public:
	MongoAccess(const char *url);
	~MongoAccess();
	using ErrCode = std::tuple<unsigned int, const char*>;
	using ResultErrCode = std::tuple<unsigned int, const char*, MongoBsonObj::Ptr>;

	MongoCollectionObj::Ptr GetCollection(const char *dbName, const char *collName);
	ResultErrCode CreateIndex(MongoCollectionObj *coll, MongoBsonObj *keyObj, MongoBsonObj *optsObj = nullptr);
	ResultErrCode FindAndMod(MongoCollectionObj *coll, MongoBsonObj *queryObj, MongoBsonObj *sortObj, MongoBsonObj *updateObj, MongoBsonObj *fieldObj, MongoFlags *flags = nullptr);
	MongoCursorObj::Ptr Find(MongoCollectionObj *coll, MongoBsonObj *queryObj, MongoBsonObj *optsObj = nullptr, mongoc_read_mode_t perfsType = MONGOC_READ_PRIMARY);
	MongoCursorObj::Ptr Aggregate(MongoCollectionObj *coll, MongoBsonObj *pipeObj, MongoBsonObj *optsObj = nullptr, mongoc_read_mode_t perfsType = MONGOC_READ_PRIMARY);
	ErrCode Insert(MongoCollectionObj *coll, MongoBsonObj *insertObj);
	ResultErrCode InsertMany(MongoCollectionObj *coll, std::vector<MongoBsonObj*> &insertVec, MongoBsonObj *optsObj = nullptr);
	ResultErrCode InsertOne(MongoCollectionObj *coll, MongoBsonObj *insertObj, MongoBsonObj *optsObj = nullptr);
	void ping();
	bool IsConn();
private:
	mongoc_client_pool_t *pool_ = nullptr;
	mongoc_uri_t *uri_ = nullptr;
	bool isConn_ = false;
};




}
}

